/* Renderer
 *
 * By: M. Etheredge
 *     S. Brattinga
 */

#include "renderer.h"

int Renderer::g_Window;

GLuint Renderer::g_Textures[1];

GLuint Renderer::g_CubeList;

Grid *Renderer::g_Grid = 0;

GLfloat Renderer::g_Mu = 0.0f;

Camera *Renderer::g_Camera;

bool Renderer::g_LargeGrid;

int Renderer::g_camRotX = 0;
int Renderer::g_camRotY = 0;

int Renderer::p_PreviousMouseX = 0;
int Renderer::p_PreviousMouseY = 0;
bool Renderer::g_Keys[128];

GLvoid Renderer::BuildList( void )
{
	g_CubeList = glGenLists( 1 );

    glNewList( g_CubeList, GL_COMPILE );
        glBegin( GL_QUADS );
            // Top Face
            glTexCoord2f( 0.0f, 1.0f ); glVertex3f( -1.0f,  1.0f, -1.0f );
            glTexCoord2f( 0.0f, 0.0f ); glVertex3f( -1.0f,  1.0f,  1.0f );
            glTexCoord2f( 1.0f, 0.0f ); glVertex3f(  1.0f,  1.0f,  1.0f );
            glTexCoord2f( 1.0f, 1.0f ); glVertex3f(  1.0f,  1.0f, -1.0f );

            // Bottom Face
            glTexCoord2f( 1.0f, 1.0f ); glVertex3f( -1.0f, -1.0f, -1.0f );
            glTexCoord2f( 0.0f, 1.0f ); glVertex3f(  1.0f, -1.0f, -1.0f );
            glTexCoord2f( 0.0f, 0.0f ); glVertex3f(  1.0f, -1.0f,  1.0f );
            glTexCoord2f( 1.0f, 0.0f ); glVertex3f( -1.0f, -1.0f,  1.0f );

            // Front Face
            glTexCoord2f( 0.0f, 0.0f ); glVertex3f( -1.0f, -1.0f,  1.0f );
            glTexCoord2f( 1.0f, 0.0f ); glVertex3f(  1.0f, -1.0f,  1.0f );
            glTexCoord2f( 1.0f, 1.0f ); glVertex3f(  1.0f,  1.0f,  1.0f );
            glTexCoord2f( 0.0f, 1.0f ); glVertex3f( -1.0f,  1.0f,  1.0f );

            // Back Face
            glTexCoord2f( 1.0f, 0.0f ); glVertex3f( -1.0f, -1.0f, -1.0f );
            glTexCoord2f( 1.0f, 1.0f ); glVertex3f( -1.0f,  1.0f, -1.0f );
            glTexCoord2f( 0.0f, 1.0f ); glVertex3f(  1.0f,  1.0f, -1.0f );
            glTexCoord2f( 0.0f, 0.0f ); glVertex3f(  1.0f, -1.0f, -1.0f );

            // Right face
            glTexCoord2f( 1.0f, 0.0f ); glVertex3f(  1.0f, -1.0f, -1.0f );
            glTexCoord2f( 1.0f, 1.0f ); glVertex3f(  1.0f,  1.0f, -1.0f );
            glTexCoord2f( 0.0f, 1.0f ); glVertex3f(  1.0f,  1.0f,  1.0f );
            glTexCoord2f( 0.0f, 0.0f ); glVertex3f(  1.0f, -1.0f,  1.0f );

            // Left Face
            glTexCoord2f( 0.0f, 0.0f ); glVertex3f( -1.0f, -1.0f, -1.0f );
            glTexCoord2f( 1.0f, 0.0f ); glVertex3f( -1.0f, -1.0f,  1.0f );
            glTexCoord2f( 1.0f, 1.0f ); glVertex3f( -1.0f,  1.0f,  1.0f );
            glTexCoord2f( 0.0f, 1.0f ); glVertex3f( -1.0f,  1.0f, -1.0f );
        glEnd();
    glEndList();
}

void Renderer::StrokeString(void* font, const char* string) {

   GLfloat length = glutStrokeLength(font, (unsigned char*)string);
   glTranslatef(-length/2.0f, 0.0f, 0.0f);

   char* p;
   p = (char*) string;
   while (*p != '\0') glutStrokeCharacter(font, *p++);
}

int Renderer::ImageLoad( char *p_Filename, Image *p_Image )
{
    FILE *file;
    unsigned long size;
    unsigned long i;
    unsigned short int planes;
    unsigned short int bpp;
    char temp;

    if( (file = fopen( p_Filename, "rb" )) == NULL )
    {
        printf( "File Not Found : %s\n", p_Filename );

        return 0;
    }

    fseek( file, 18, SEEK_CUR );

    if( (i = fread( &p_Image->sizeX, 4, 1, file )) != 1 )
    {
        return 0;
    }

    if( (i = fread( &p_Image->sizeY, 4, 1, file )) != 1 )
    {
        return 0;
    }

    size = p_Image->sizeX * p_Image->sizeY * 3;

    if( (fread( &planes, 2, 1, file )) != 1 )
    {
        return 0;
    }
    if( planes != 1 )
    {
        return 0;
    }

    if( (i = fread( &bpp, 2, 1, file )) != 1 )
    {
        return 0;
    }
    if( bpp != 24 )
    {
        printf( "Bpp from %s is not 24: %u\n", p_Filename, bpp );

        return 0;
    }

    fseek( file, 24, SEEK_CUR );

    p_Image->data = (char *) malloc( size );
    if( p_Image->data == NULL )
    {
        return 0;
    }

    if( (i = fread( p_Image->data, size, 1, file )) != 1 )
    {
        return 0;
    }

    for( i = 0 ; i < size ; i += 3 )
    {
        temp = p_Image->data[i];
        p_Image->data[i] = p_Image->data[i+2];
        p_Image->data[i+2] = temp;
    }

    return 1;
}

void Renderer::LoadGLTextures( void )
{
    Image *image1;

    image1 = (Image *) malloc( sizeof( Image ) );
    if( image1 == NULL )
    {
        puts( "Error allocating space for image" );
        exit( 0 );
    }

    if( !ImageLoad( (char *) "Data/teaglass/glass.bmp", image1 ) )
    {
    	puts( "Could not load textures" );
        return;
    }

    // Create Texture
    glGenTextures( 1, &g_Textures[0] );
    glBindTexture( GL_TEXTURE_2D, g_Textures[0] );

    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST );

    gluBuild2DMipmaps( GL_TEXTURE_2D, 3, image1->sizeX, image1->sizeY, GL_RGB, GL_UNSIGNED_BYTE, image1->data );
};

void Renderer::InitGL( int p_Width, int p_Height )
{
	const GLfloat w[] = { 1.0, 1.0, 1.0, 1.0 };

    LoadGLTextures();
    BuildList();

    // Enable texture mapping
    glEnable( GL_TEXTURE_2D );

    glClearColor( 0.0f, 0.0f, 0.0f, 0.0f );	// Give us a black clear colour
    glClearDepth( 1.0 );				    // Enables Clearing Of The Depth Buffer
    glDepthFunc( GL_LESS );                 // GL_LESS depth testing: http://www.opengl.org/sdk/docs/man/xhtml/glDepthFunc.xml
    glEnable( GL_DEPTH_TEST );              // Enable depth testing
    //glShadeModel( GL_SMOOTH );              // Enable smooth shading

    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();                       // Load identity

    gluPerspective( 45.0f, (GLfloat) p_Width / (GLfloat) p_Height, 0.1f, 100.0f );	// Calculate the aspect ratio

    glMatrixMode( GL_MODELVIEW );

    if( !g_LargeGrid )
    {
		glEnable( GL_BLEND );
		glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
    }

    glEnable(GL_POLYGON_SMOOTH);

    glEnable( GL_LIGHT0 );                  // Enable light 0
    glEnable( GL_LIGHTING );                // Enable lighting

	glLightfv( GL_LIGHT0, GL_DIFFUSE, w );
	glLightfv( GL_LIGHT0, GL_SPECULAR, w );

    glEnable( GL_COLOR_MATERIAL );          // Enable texture colouring

    glEnable( GL_CULL_FACE );

    g_Camera = new Camera( 0.0f, 0.0f, 0.0f );
}

void Renderer::ResizeGLScene( int p_Width, int p_Height )
{
    if( p_Height == 0 )
    {
    	p_Height = 1;
    }

    glViewport( 0, 0, p_Width, p_Height );

    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();

    gluPerspective( 80.0f, (GLfloat) p_Width / (GLfloat) p_Height, 0.1f, 200.0f );
    glMatrixMode( GL_MODELVIEW );
}

void Renderer::DrawGLScene( void )
{
	static const GLfloat l[] = { 0.0, 0.0, 0.0, 1.0 };

	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

	glPushMatrix();
		glTranslatef( 8.0 * sin( g_Mu ), 1.0, 8.0 * cos( g_Mu ) );
		glLightfv( GL_LIGHT0, GL_POSITION, l );
	glPopMatrix();

	glLoadIdentity();

    glDisable( GL_TEXTURE_2D );
        if( g_Grid != 0 )
        {
            g_Grid->Render();
        }
    glEnable( GL_TEXTURE_2D );

    Renderer::DrawSkybox();
    Renderer::DrawKeyboardMovement();

    glutWarpPointer( 1280 * 0.5f, 800 * 0.5f );
	glutSwapBuffers();
}

void Renderer::DrawSkybox( void )
{
    glScalef( 5000.0f, 5000.0f, 5000.0f );
    glCallList( g_CubeList );
}

void Renderer::Cleanup( void )
{
	delete g_Grid;

	puts( "Done." );
}

void Renderer::DrawKeyboardMovement( void )
{
    if( g_Keys['w'] == true )
    {
        g_Camera->MoveLoc( 0, 0, 1, 0.25f );
    }
    if( g_Keys['s'] == true )
    {
        g_Camera->MoveLoc( 0, 0, -1, 0.25f );
    }
    if( g_Keys['a'] == true )
    {
        g_Camera->MoveLoc( -1, 0, 0, 0.25f );
    }
    if( g_Keys['d'] == true )
    {
        g_Camera->MoveLoc( 1, 0, 0, 0.25f );
    }
    if( g_Keys[32] == true )
    {
        g_Camera->MoveLoc( 0, 1, 0, 0.25f );
    }
    if( g_Keys['c'] == true )
    {
        g_Camera->MoveLoc( 0, -1, 0, 0.25f );
    }
}

void Renderer::KeyPressed( unsigned char p_Key, int p_X, int p_Y )
{
    usleep( 100 );

    if( p_Key == 27 )
    {
    	Cleanup();
		glutDestroyWindow( g_Window );
		exit( 0 );
    }

    g_Keys[p_Key] = true;
}

void Renderer::KeyReleased( unsigned char p_Key, int p_X, int p_Y )
{
    usleep( 100 );

    g_Keys[p_Key] = false;
}

void Renderer::SpecialKeyPressed( int p_Key, int p_X, int p_Y )
{
    usleep( 100 );

    switch( p_Key )
    {
        case GLUT_KEY_UP:
            break;
        case GLUT_KEY_DOWN:
            break;
        case GLUT_KEY_LEFT:
            g_Mu += 0.001f;
            break;
        case GLUT_KEY_RIGHT:
            g_Mu += 0.001f;
            break;
    }
}

void Renderer::MouseMotion( int p_X, int p_Y )
{
    g_Camera->RotateLoc( (p_X - 640) * 0.25f, 0.0f, 1.0f, 0.0f );
	g_Camera->RotateLoc( (p_Y - 400) * 0.25f, 1.0f, 0.0f, 0.0f );
	g_Mu += (p_X - 640) * 0.001f;
}

void Renderer::Run( int argc, char **argv )
{
	glutInit( &argc, argv );
	glutInitDisplayMode( GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH );
	glutInitWindowSize( 1280, 800 );
	glutInitWindowPosition( 0, 0 );

	Renderer::g_Window = glutCreateWindow( "Teaglass" );

	glutDisplayFunc( &Renderer::DrawGLScene );
	glutSetCursor( GLUT_CURSOR_NONE );
	glutIgnoreKeyRepeat( true );

	glutFullScreen();

	glutIdleFunc( &Renderer::DrawGLScene );
	glutReshapeFunc( &Renderer::ResizeGLScene );
	glutKeyboardFunc( &Renderer::KeyPressed );
	glutKeyboardUpFunc( &Renderer::KeyReleased );
	glutSpecialFunc( &Renderer::SpecialKeyPressed );
	glutPassiveMotionFunc( &Renderer::MouseMotion );

	Renderer::InitGL( 1280, 800 );

	glutMainLoop();
}
